import java.net.InetSocketAddress;
import java.net.InetAddress;
import java.util.ArrayList;

public class Test {

	private MulticastQueue.DeliveryGuarantee deliveryGuarantee;
	private int peers;
	private int messages;
	int port;
	private boolean prints = true;

	public Test(MulticastQueue.DeliveryGuarantee deliveryGuarantee, int peers, int messages, boolean prints, int port) throws Exception {
		this.deliveryGuarantee = deliveryGuarantee;
		this.peers = peers;
		this.messages = messages;
		this.prints = prints;
		this.port = port;
	}

	public long start() throws Exception {
		long startTime = Long.MAX_VALUE;
		long endTime = 0;
		InetAddress localhost = InetAddress.getLocalHost();

		MulticastQueue<Integer>[] peer;
		if (deliveryGuarantee == MulticastQueue.DeliveryGuarantee.FIFO) {
			peer = new MulticastQueueFifoOnly[peers];
		} else if (deliveryGuarantee == MulticastQueue.DeliveryGuarantee.TOTAL) {
			peer = new MulticastQueueTotalOnly[peers];
		} else {
			peer = null;
		}

		for (int p=0; p<peers; p++) {
			if (deliveryGuarantee == MulticastQueue.DeliveryGuarantee.FIFO) {
				peer[p] = new MulticastQueueFifoOnly<Integer>(); 
			} else if (deliveryGuarantee == MulticastQueue.DeliveryGuarantee.TOTAL) {
				peer[p] = new MulticastQueueTotalOnly<Integer>(); 
			}
		}

		if (prints) System.out.println("FORMING GROUP");

		peer[0].createGroup(port, deliveryGuarantee);
		wait(1);

		for (int i=1; i<peers; i++) {
			peer[i].joinGroup((i+port), new InetSocketAddress(localhost,(i+port-1)), deliveryGuarantee);
			wait(1);
		}

		if (prints) System.out.println("BROADCASTING");

		for (int i=0; i<messages; i++) {
			peer[i % peers].put(Integer.valueOf(i));
		}

		wait(12);

		if (prints) System.out.println("LEAVING GROUP");

		for (int i=0; i<peers; i++) {
			peer[i].leaveGroup();
			wait(1);
		}

		ArrayList<ArrayList<MulticastMessage>> order = new ArrayList<ArrayList<MulticastMessage>>();

		for (int p = 0; p<peers; p++) {
			if (prints) System.out.println("RECEIVED BY peer["+p+"]");
			boolean[] received = new boolean[messages];
			MulticastMessage msg;
			ArrayList<MulticastMessage> msgs = new ArrayList<MulticastMessage>();
			while ((msg = peer[p].get()) != null) {
				if (prints) System.out.println("       " + msg);
				if (msg instanceof MulticastMessagePayload) {
					msgs.add(msg);
					if (msg.getEndTime() > endTime) {
						endTime = msg.getEndTime();
					}
					if (msg.getEndTime() < startTime) {
						startTime = msg.getEndTime();
					}
					int theInt = ((MulticastMessagePayload<Integer>)msg).getPayload().intValue();
					if (received[theInt]==true) {
						System.out.println("Test result: FAIL!\n   Reason: Peer " + p + " received " + theInt + " twice");
						System.exit(-1);
					} 
					received[theInt]=true;
				}
			}
			order.add(msgs);
			for (int i=0; i<messages; i++) {
				if (received[i]==false) {
					System.out.println("Test result: FAIL!\n   Reason: Peer " + p + " did not receive " + i);
					System.out.println("Acknowledgements: " + ((MulticastQueueTotalOnly) peer[p]).acknowledgements);
					System.exit(-1);
				} 			
			}
		}

		boolean equality = true;
		ArrayList<MulticastMessage> first = order.get(0);
		for (ArrayList<MulticastMessage> a : order) {
			for (int i=0; i<a.size(); i++) {
				if (!a.get(i).toString().equals(first.get(i).toString())) {
					equality = false;
				}
			}
		}

		if (prints) System.out.println("\nTotally ordered multicast: " + equality); 
		if (prints) System.out.println("\nRunning time: " +  (endTime-startTime) + " milliseconds");
		if (prints) System.out.println("\nTest result: OK!");
		return endTime - startTime;
	}

	public void wait(int secs) {
		try {
			if (prints) System.out.println("Test thread sleeping: " + secs + " second(s)");
			Thread.sleep(secs*1000);
		} catch (Exception e) {}
	}
}
